media_pp\elements\driver\webrtc/track.rs
1use std::{
2 sync::{
3 Arc, Mutex,
4 atomic::{AtomicU64, Ordering},
5 },
6 time::Duration,
7};
8
9use crate::pp_log::{PpLog, pp_error, pp_info};
10use crossbeam_channel::{Receiver, Sender, TrySendError, select};
11use str0m::{
12 change::{SdpAnswer, SdpOffer},
13 format::Codec,
14 media::{Direction, MediaKind, Mid},
15};
16
17use crate::{
18 buffer::MediaBuffer,
19 bus::{Bus, BusEvent},
20 control::{
21 ControlMsg, ControlReceiver, RequestKind, apply_finish, apply_one, drain_control,
22 wait_out_pause,
23 },
24 element::{Element, ElementType, Sink, Source, SourceElement, element_pp_log},
25 error::Result,
26 pad::SrcPad,
27};
28
29use super::command::{Command, TrackId, WebRtcError};
30
31/// Cheaply-cloneable handle for requesting new tracks, completing
32/// renegotiation, and picking up newly-attached tracks — same spirit as
33/// [`crate::elements::AppSourceHandle`]. Cloning shares one queue of
34/// pending [`WebRtcHandle::next_track`] results, same as any other
35/// multi-consumer channel — only one clone's call actually receives a
36/// given track, so in practice only one place in the app should be
37/// draining it.
38#[derive(Clone)]
39pub struct WebRtcHandle {
40 pub(super) next_id: Arc<AtomicU64>,
41 pub(super) command_tx: Sender<Command>,
42 pub(super) new_track_rx:
43 Receiver<(TrackId, Mid, MediaKind, WebRtcTrackSink, WebRtcTrackSource)>,
44}
45
46impl WebRtcHandle {
47 /// Requests a new track of `kind`/`direction`. Blocks only while the
48 /// peer's bounded command queue is full; once the command is accepted,
49 /// returns the locally assigned [`TrackId`]. This does not mean SDP
50 /// negotiation has completed — receive the attached track through
51 /// [`WebRtcHandle::next_track`]. Returns [`WebRtcError::Closed`] without
52 /// yielding a `TrackId` if the peer loop has already stopped.
53 ///
54 /// `codec` is what [`WebRtcTrackSink::consume`] on the resulting track
55 /// will actually be fed (an encoder's output, or a packet relayed
56 /// verbatim from another track) — used to pick the matching payload
57 /// type out of whatever this connection negotiates for the track,
58 /// instead of guessing. If this connection never negotiates `codec` for
59 /// it, pushed buffers are silently dropped, same as an unopened track.
60 pub fn add_track(
61 &self,
62 kind: MediaKind,
63 direction: Direction,
64 codec: Codec,
65 ) -> Result<TrackId> {
66 let id = TrackId(self.next_id.fetch_add(1, Ordering::Relaxed));
67 self.command_tx
68 .send(Command::AddTrack(id, kind, direction, codec))
69 .map_err(|_| WebRtcError::Closed)?;
70 Ok(id)
71 }
72
73 /// Blocks until the next track attaches — either one requested via
74 /// [`WebRtcHandle::add_track`] (on either side) once its `Mid` exists,
75 /// or one the remote peer added on its own. Returns the `TrackId` (so
76 /// the caller can match it against what `add_track` returned — a `Mid`
77 /// alone doesn't exist yet at `add_track` time, see [`TrackId`]'s own
78 /// docs) alongside the `Mid`/`MediaKind` str0m assigned it and the
79 /// `WebRtcTrackSink`/`WebRtcTrackSource` pair to send/receive on it.
80 /// `Err` once `WebRtcPeer` (and its `run`) is gone and every
81 /// already-attached track has been drained.
82 pub fn next_track(
83 &self,
84 ) -> Result<(TrackId, Mid, MediaKind, WebRtcTrackSink, WebRtcTrackSource)> {
85 self.new_track_rx
86 .recv()
87 .map_err(|_| WebRtcError::Closed.into())
88 }
89
90 /// Feeds a remote answer back in, completing a renegotiation started by
91 /// [`WebRtcHandle::add_track`]. A no-op if `WebRtcPeer` (and its `run`)
92 /// is already gone.
93 pub fn set_answer(&self, answer: SdpAnswer) {
94 let _ = self.command_tx.send(Command::SetAnswer(answer));
95 }
96
97 /// Accepts a fresh offer from the *remote* peer (their own
98 /// renegotiation) and returns the resulting answer for the caller to
99 /// ship back over its own signaling transport. Blocks until
100 /// `WebRtcPeer::run` has actually applied it.
101 pub fn accept_remote_offer(&self, offer: SdpOffer) -> Result<SdpAnswer> {
102 let (reply_tx, reply_rx) = crossbeam_channel::bounded(0);
103 self.command_tx
104 .send(Command::AcceptOffer(offer, reply_tx))
105 .map_err(|_| WebRtcError::Closed)?;
106 reply_rx
107 .recv()
108 .map_err(|_| WebRtcError::Closed)?
109 .map_err(Into::into)
110 }
111}
112
113/// One outbound track. A plain [`Sink`] — no bespoke push API, it links
114/// into a [`crate::pipeline::ChainBuilder`] exactly like
115/// [`crate::elements::RtspSink`] or any other terminal sink.
116/// `consume()` only ever hands off to `WebRtcPeer::run`'s own thread via a
117/// channel send; the actual str0m write happens over there.
118pub struct WebRtcTrackSink {
119 pp_log: PpLog,
120 id: TrackId,
121 command_tx: Sender<Command>,
122}
123
124impl WebRtcTrackSink {
125 pub(super) fn new(id: TrackId, command_tx: Sender<Command>) -> Self {
126 Self {
127 id,
128 command_tx,
129 pp_log: element_pp_log(
130 ElementType::WebRtcPeer,
131 &format!("webrtc-track-{}", id.0),
132 None,
133 ),
134 }
135 }
136}
137
138impl Element for WebRtcTrackSink {
139 fn name(&self) -> Arc<str> {
140 format!("webrtc-track-{}", self.id.0).into()
141 }
142
143 fn element_type(&self) -> ElementType {
144 ElementType::WebRtcPeer
145 }
146
147 fn pp_log(&self) -> &PpLog {
148 &self.pp_log
149 }
150
151 fn pp_log_mut(&mut self) -> &mut PpLog {
152 &mut self.pp_log
153 }
154}
155
156impl Sink for WebRtcTrackSink {
157 fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
158 if !matches!(buf, MediaBuffer::Packet(_) | MediaBuffer::Eos) {
159 let kind = match buf {
160 MediaBuffer::Video(_) => "Video",
161 MediaBuffer::Audio(_) => "Audio",
162 MediaBuffer::Packet(_) | MediaBuffer::Eos => unreachable!("matched above"),
163 };
164 pp_error!(self, "unsupported buffer: {kind}");
165 return Err(WebRtcError::UnsupportedBuffer(kind).into());
166 }
167 // `WebRtcPeer::run` gone (channel disconnected) means this track is
168 // dead — surface it as `Err` rather than swallowing it, so whatever
169 // pipeline this `Sink` is plugged into (its own `Queue`, its own
170 // `Bus`) actually learns about it instead of silently sending into
171 // a void forever. Non-fatal by the same convention as any other
172 // `Sink::consume` failure (see `Queue`'s own docs) — just no longer
173 // an invisible one.
174 //
175 // A full channel (`WebRtcPeer::run` backed up) drops the newest
176 // buffer instead — same as an unopened track (see `add_track`'s
177 // docs) — but isn't reported on a `Bus`: unlike `WebRtcPeer::run`,
178 // which only ever borrows a `Bus` for the duration of one `run()`
179 // call, `WebRtcTrackSink` is a handle the caller can keep past
180 // `Driver::stop()`, so storing one here would keep that `Bus`'s
181 // channel open indefinitely — including past whatever's waiting on
182 // `BusReceiver::iter()` to finish once every sender is gone.
183 match self.command_tx.try_send(Command::Push(self.id, buf)) {
184 Ok(()) | Err(TrySendError::Full(_)) => Ok(()),
185 Err(TrySendError::Disconnected(_)) => {
186 pp_error!(self, "WebRtcPeer::run gone — track is dead");
187 Err(WebRtcError::Closed.into())
188 }
189 }
190 }
191
192 fn control(&mut self, _msg: ControlMsg) -> Result<()> {
193 // Terminal, same as AppSink/RtspSink: nothing buffered or
194 // downstream to flush/forward for any ControlMsg.
195 Ok(())
196 }
197}
198
199/// One inbound track — the mirror image of [`WebRtcTrackSink`]. A plain
200/// [`SourceElement`], same shape as [`crate::elements::AppSource`]: it
201/// links into its own [`crate::pipeline::Pipeline`] via `src_pads()` like
202/// any other source. The difference from `AppSource` is only *who* feeds
203/// it — instead of an [`crate::elements::AppSourceHandle`] the app calls
204/// itself, [`crate::driver::Driver::run`] pushes into the sending half of this same
205/// channel internally, from its own thread, for every `Event::MediaData`
206/// on this track's `Mid`. Nothing here ever calls back into caller-supplied
207/// code from `WebRtcPeer::run`'s own thread — that thread only ever touches
208/// this crate's own types (see the module docs for why `WebRtcPeer` hands
209/// tracks out through [`WebRtcHandle::next_track`] instead of a callback).
210pub struct WebRtcTrackSource {
211 pp_log: PpLog,
212 name: Arc<str>,
213 pad: SrcPad,
214 data_rx: Receiver<MediaBuffer>,
215 codec: Arc<Mutex<Option<Codec>>>,
216}
217
218impl WebRtcTrackSource {
219 pub(super) fn new(
220 name: impl Into<String>,
221 data_rx: Receiver<MediaBuffer>,
222 codec: Arc<Mutex<Option<Codec>>>,
223 ) -> Self {
224 let name: Arc<str> = name.into().into();
225 let pp_log = element_pp_log(ElementType::WebRtcPeer, &name, None);
226 let pad = SrcPad::new(format!("{name}_src"));
227 Self {
228 name,
229 pp_log,
230 pad,
231 data_rx,
232 codec,
233 }
234 }
235
236 /// The codec this track is actually carrying, as seen on the most
237 /// recently received packet's RTP payload type — `None` until the
238 /// first one arrives. Unlike [`WebRtcHandle::add_track`]'s `codec`
239 /// (which the *caller* declares up front for an outbound track), an
240 /// inbound track's codec isn't knowable ahead of time: SDP negotiation
241 /// can accept several codecs for one `m=` line, and only the packets
242 /// actually arriving say which one the remote side picked (see
243 /// `Event::MediaData`'s own `params` field). Whatever's downstream
244 /// (e.g. a decoder) needs a keyframe before it can do anything useful
245 /// anyway, so waiting for the first packet to learn the codec isn't an
246 /// extra constraint in practice.
247 pub fn codec(&self) -> Option<Codec> {
248 *self.codec.lock().unwrap()
249 }
250}
251
252impl Element for WebRtcTrackSource {
253 fn name(&self) -> Arc<str> {
254 self.name.clone()
255 }
256
257 fn element_type(&self) -> ElementType {
258 ElementType::WebRtcPeer
259 }
260
261 fn pp_log(&self) -> &PpLog {
262 &self.pp_log
263 }
264
265 fn pp_log_mut(&mut self) -> &mut PpLog {
266 &mut self.pp_log
267 }
268}
269
270impl Source for WebRtcTrackSource {
271 fn src_pads(&mut self) -> &mut [SrcPad] {
272 std::slice::from_mut(&mut self.pad)
273 }
274}
275
276impl SourceElement for WebRtcTrackSource {
277 /// Identical shape to [`crate::elements::AppSource::run`]: selects on
278 /// `control` and its own data channel together, so `Stop`/`Pause`
279 /// never wait behind a remote peer that's gone quiet. The data channel
280 /// disconnecting — `WebRtcPeer` gone, whether from `Stop` or the
281 /// connection dying on its own — ends this the same way `AppSource`
282 /// ends when every `AppSourceHandle` is dropped: one final `Eos`, no
283 /// error.
284 fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
285 pp_info!(self, "started");
286 loop {
287 if drain_control(control, self, bus)?.stopped {
288 pp_info!(self, "stopped");
289 return Ok(());
290 }
291
292 select! {
293 recv(control.rx) -> req => {
294 match req {
295 Ok(req) => {
296 match req.kind {
297 RequestKind::Finish => {
298 apply_finish(self, bus, &req.ack);
299 pp_info!(self, "finished");
300 return Ok(());
301 }
302 RequestKind::Control(msg) => {
303 if apply_one(self, bus, msg, &req.ack)? {
304 pp_info!(self, "stopped");
305 return Ok(());
306 }
307 if msg == ControlMsg::Pause
308 && wait_out_pause(control, self, bus)?
309 {
310 pp_info!(self, "stopped");
311 return Ok(());
312 }
313 }
314 }
315 }
316 // The Pipeline itself is gone — nothing left to drive this.
317 Err(_) => {
318 pp_info!(self, "run: control channel gone, ending");
319 return Ok(());
320 }
321 }
322 }
323 recv(self.data_rx) -> buf => {
324 match buf {
325 Ok(buf) if buf.is_eos() => {
326 pp_info!(self, "event=eos phase=source_received");
327 break;
328 }
329 Ok(buf) => {
330 if let Err(error) = self.pad.push(buf) {
331 bus.post(
332 &self.pp_log,
333 BusEvent::Error {
334 element_type: ElementType::WebRtcPeer,
335 name: self.name.clone(),
336 error,
337 },
338 );
339 }
340 }
341 // `WebRtcPeer` gone — this track (or the whole peer) is done.
342 Err(_) => {
343 pp_info!(self, "run: WebRtcPeer gone, ending");
344 break;
345 }
346 }
347 }
348 }
349 }
350 // The data channel ending (above) can race a `Stop` sent at the
351 // same moment — e.g. stopping the *upstream* `WebRtcPeer` (via its
352 // `DriverRunner`) disconnects this exact channel, and a caller
353 // stopping this `Pipeline` too, right after, can land its `Stop` in
354 // `control`'s queue after `select!` already picked the data arm.
355 // Ack it (a no-op otherwise) so `ControlSender::send`'s rendezvous
356 // never blocks forever waiting for an ack this thread would
357 // otherwise never get around to sending.
358 while let Some((_msg, ack)) = control.try_recv() {
359 let _ = ack.send(());
360 }
361 self.pad.push_eos(&self.pp_log)
362 }
363
364 /// No timeline of its own — same reasoning as
365 /// [`crate::elements::AppSource::seek`]: a WebRTC connection has
366 /// nothing to reposition.
367 fn seek(&mut self, target: Duration) -> Result<Duration> {
368 Ok(target)
369 }
370}